package batch.status;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;

import batch.util.ParameterUtil;

import common.db.jdbc.Jdbc;

import core.config.Env;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;
import core.util.ArrayUtil;

/**
 * ジョブ管理テーブル取得／更新クラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class JobStatusImpl implements JobStatus {

	/** テーブル名 ジョブ管理 */
	private static final String TABLE = Env.getEnv("JobStatus.Table");

	/** カラム名 ジョブ連番 */
	private static final String COL_JOB_SEQ = Env.getEnv("JobStatus.JobSeq");
	/** カラム名 ジョブID */
	private static final String COL_JOB_ID = Env.getEnv("JobStatus.JobId");
	/** カラム名 ユーザID */
	private static final String COL_USER_ID = Env.getEnv("JobStatus.UserId");
	/** カラム名 IPアドレス */
	private static final String COL_IP_ADD = Env.getEnv("JobStatus.Ip");
	/** カラム名 ジョブパラメタ */
	private static final String COL_JOB_PRM = Env.getEnv("JobStatus.Param");
	/** カラム名 バッチ実行パラメタ */
	private static final String COL_JOB_EXE_PRM = Env.getEnv("JobStatus.ExecParam");
	/** カラム名 画面パラメタ */
	private static final String COL_GAMEN_PRM = Env.getEnv("JobStatus.Display");
	/** カラム名 ジョブステータス */
	private static final String COL_JOB_STS = Env.getEnv("JobStatus.Sts");
	/** カラム名 備考 */
	private static final String COL_BIKOU = Env.getEnv("JobStatus.Biko");
	/** カラム名 登録日時 */
	private static final String COL_TOROKU_DTM = Env.getEnv("JobStatus.Entry");
	/** カラム名 ジョブ名称 */
	private static final String COL_JOB_MEI = Env.getEnv("JobStatus.JobMei");
	/** カラム名 ホストID */
	private static final String COL_HOST_ID = Env.getEnv("JobStatus.Host");
	/** カラム名 メッセージテキスト */
	private static final String COL_MSG_TXT = Env.getEnv("JobStatus.MsgTxt");

	/** 連番取得SQL */
	private static final String SQL_BATCH_NEXTVAL = Env.getEnv("Batch.SqlNextVal");

	/** 作成SQL */
	private static final String SQL_INSERT;

	static {
		if (!Objects.toString(SQL_BATCH_NEXTVAL, "").isEmpty()) {
			SQL_INSERT = String.format(
					"INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"
				+ " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", TABLE,
				COL_JOB_SEQ, COL_JOB_ID, COL_USER_ID, COL_TOROKU_DTM, COL_JOB_PRM, COL_GAMEN_PRM,
				COL_JOB_MEI, COL_IP_ADD, COL_HOST_ID, COL_JOB_STS, COL_BIKOU, COL_JOB_EXE_PRM);
		} else {
			SQL_INSERT = String.format(
					"INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"
				+ " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", TABLE,
				COL_JOB_ID, COL_USER_ID, COL_TOROKU_DTM, COL_JOB_PRM, COL_GAMEN_PRM,
				COL_JOB_MEI, COL_IP_ADD, COL_HOST_ID, COL_JOB_STS, COL_BIKOU, COL_JOB_EXE_PRM);
		}
	}

	/**
	 * テーブル名取得
	 *
	 * @return テーブル名
	 */
	@Override
	public String getTableName() {
		return TABLE;
	}

	/**
	 * ジョブ連番取得処理
	 *
	 * @param conn コネクション
	 * @return ジョブ連番
	 */
	private long getNextJobSeq(final Connection conn) {
		if (Objects.toString(SQL_BATCH_NEXTVAL, "").isEmpty()) {
			return 0;
		}

		try (PreparedStatement psmt = conn.prepareStatement(SQL_BATCH_NEXTVAL)) {
			long ret = -1;
			try (ResultSet rs = psmt.executeQuery()) {
				if (rs.next()) {
					ret = rs.getLong(1);
				}
			}
			return ret;

		} catch (SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * ジョブ管理状態取得
	 *
	 * @param conn コネクション
	 * @param hostId ホストID
	 * @param ts タイムスタンプ
	 * @return ジョブ管理テーブル情報
	 */
	@Override
	public List<Job> getJobListByTimestamp(final Connection conn,
			final String hostId, final Timestamp ts) {
		String query;
		if (hostId != null) {
			query = String.format("SELECT * FROM %s WHERE %s <= ? AND %s = ? FOR UPDATE ",
					TABLE, COL_TOROKU_DTM, COL_HOST_ID);
		} else {
			query = String.format("SELECT * FROM %s WHERE %s <= ? FOR UPDATE ",
					TABLE, COL_TOROKU_DTM);
		}

		try (PreparedStatement psmt = Jdbc.wrap(conn).readonlyStatement(query)) {
			psmt.setTimestamp(1, ts);
			if (hostId != null) {
				psmt.setString(2, hostId);
			}

			List<Job> l = new ArrayList<>();
			try (ResultSet rs = psmt.executeQuery()) {
				while (rs.next()) {
					l.add(toJobObject(rs));
				}
			}
			return l;

		} catch (SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * ジョブ管理テーブル項目取得（排他）
	 *
	 * @param conn コネクション
	 * @param seq ジョブ連番
	 * @return ジョブ管理テーブル情報
	 */
	@Override
	public Job getJobWithLock(final Connection conn, final long seq) {
		final String query = String.format(
				"SELECT * FROM %s WHERE %s = ? FOR UPDATE", TABLE, COL_JOB_SEQ);

		try (PreparedStatement psmt = Jdbc.wrap(conn).readonlyStatement(query)) {
			psmt.setLong(1, seq);

			Job ret = null;
			try (ResultSet rs = psmt.executeQuery()) {
				if (rs.next()) {
					ret = toJobObject(rs);
				}
			}
			return ret;

		} catch (SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * ジョブ管理状態更新
	 *
	 * @param seq ジョブ連番
	 * @param sts ステータス
	 * @param msg メッセージ
	 * @param conn コネクション
	 * @return 更新レコード数
	 */
	@Override
	public int updateJobStatus(final Connection conn, final long seq,
			final String host, final String msg, final JobState sts) {
		final String sql = String.format(
				"UPDATE %s SET %s = ?, %s = ?, %s = ? WHERE %s = ? ",
				TABLE, COL_JOB_STS, COL_HOST_ID, COL_MSG_TXT, COL_JOB_SEQ);

		try (PreparedStatement psmt = Jdbc.wrap(conn).prepareStatement(sql)) {

			int i = 1;
			psmt.setInt(i++, sts.value());
			psmt.setString(i++, host);
			psmt.setString(i++, msg);
			psmt.setLong(i++, seq);
			return psmt.executeUpdate();

		} catch (SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * ジョブ管理状態取得
	 *
	 * @param conn コネクション
	 * @param hostId ホストID
	 * @param sts ステータス
	 * @return ジョブ管理テーブル情報
	 */
	@Override
	public List<Job> getJobListByStatus(final Connection conn,
			final String hostId, final EnumSet<JobState> sts) {
		String query = String.format(
				"SELECT * FROM %s %s ORDER BY %s", TABLE, where(hostId, sts), COL_TOROKU_DTM);

		try (PreparedStatement psmt = Jdbc.wrap(conn).readonlyStatement(query)) {
			// バッチ管理テーブル読み込み
			setToStatement(psmt, hostId, sts);

			List<Job> l = new ArrayList<>();
			try (ResultSet rs = psmt.executeQuery()) {
				while (rs.next()) {
					l.add(toJobObject(rs));
				}
			}
			return l;

		} catch (SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * WHERE句作成
	 * @param hostId ホストID
	 * @param sts JobState
	 * @return WHERE句
	 */
	private String where(final String hostId, final EnumSet<JobState> sts) {
		StringBuilder sb = new StringBuilder();
		if (!sts.isEmpty()) {
			sb.append(COL_JOB_STS).append(" IN (");
			sb.append(ArrayUtil.concatenate(",", ArrayUtil.fill("?", new String[sts.size()])));
			sb.append(") ");
		}

		if (hostId != null) {
			if (0 < sb.length()) {
				sb.append(" AND ");
			}
			sb.append(COL_HOST_ID).append(" = ? ");
		}

		if (0 < sb.length()) {
			return " WHERE " + sb.toString();
		}

		return "";
	}

	/**
	 * パラメタ設定
	 * @param psmt ステートメント
	 * @param hostId ホストID
	 * @param sts JobState
	 * @throws SQLException SQL例外
	 */
	private void setToStatement(final PreparedStatement psmt,
			final String hostId, final EnumSet<JobState> sts) throws SQLException {
		int i = 1;
		for (final JobState js : sts) {
			psmt.setInt(i++, js.value());
		}
		if (hostId != null) {
			psmt.setString(i, hostId);
		}
	}

	/**
	 * ジョブ管理レコード作成
	 *
	 * @param conn コネクション
	 * @param job ジョブ管理
	 * @return 採番された番号（1以上）
	 */
	@Override
	public long insertJob(final Connection conn, final Job job) {

		// ジョブ管理レコード作成
		try {
			long ret = getNextJobSeq(conn);
			if (ret == 0) {
				try (PreparedStatement psmt = Jdbc.wrap(conn).prepareStatement(
						SQL_INSERT, Statement.RETURN_GENERATED_KEYS)) {
					if (!insert(psmt, 1, job)) {
						return -1;
					}

					try (ResultSet rs = psmt.getGeneratedKeys()) {
						if (rs.next()) {
							return rs.getInt(1);
						}
					}
				}
			} else if (0 < ret) {
				try (PreparedStatement psmt = Jdbc.wrap(conn).prepareStatement(SQL_INSERT)) {
					psmt.setLong(1, ret);
					if (!insert(psmt, 2, job)) {
						return -1;
					}
					return ret;
				}
			}
			return 0;

		} catch (SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * レコード作成
	 * @param psmt PreparedStatement
	 * @param loc 設定位置
	 * @param job ジョブ管理
	 * @return 作成された場合 true を返す。
	 * @throws SQLException SQL例外
	 */
	private boolean insert(final PreparedStatement psmt,
			final int loc, final Job job) throws SQLException {
		int i = loc;
		psmt.setString(i++, job.getJobId());
		psmt.setString(i++, job.getUid());
		psmt.setTimestamp(i++, job.getDateTime());
		psmt.setString(i++, ParameterUtil.toParameter(job.getJobParam()));
		psmt.setString(i++, job.getGamenParam());
		psmt.setString(i++, job.getJobName());
		psmt.setString(i++, job.getIp());
		psmt.setString(i++, job.getHostId());
		psmt.setInt(i++, job.getJobSts());
		psmt.setString(i++, job.getRemark());
		psmt.setString(i++, ParameterUtil.toParameter(job.getExecParam()));
		return psmt.executeUpdate() == 1;
	}

	/**
	 * ジョブ管理レコード削除
	 *
	 * @param conn コネクション
	 * @param seq ジョブ連番
	 * @return 削除件数
	 */
	@Override
	public int deleteJob(final Connection conn, final long seq) {
		final String sql = String.format("DELETE FROM %s WHERE %s = ? ", TABLE, COL_JOB_SEQ);

		try (PreparedStatement psmt = Jdbc.wrap(conn).prepareStatement(sql)) {
			psmt.setLong(1, seq);
			return psmt.executeUpdate();
		} catch (SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * ジョブ管理テーブル項目取得
	 *
	 * @param conn コネクション
	 * @param seq バッチ処理番号
	 * @return 取得項目値
	 */
	@Override
	public Job getJob(final Connection conn, final long seq) {
		final String query = String.format("SELECT * FROM %s WHERE %s = ? ", TABLE, COL_JOB_SEQ);

		try (PreparedStatement psmt = Jdbc.wrap(conn).readonlyStatement(query)) {
			// ジョブ管理テーブル読み込み
			psmt.setLong(1, seq);

			Job ret = null;
			try (ResultSet rs = psmt.executeQuery()) {
				if (rs.next()) {
					ret = toJobObject(rs);
				}
			}
			return ret;

		} catch (SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * ジョブオブジェクト取得
	 *
	 * @param rs 結果セット
	 * @return ジョブオブジェクト
	 * @throws SQLException SQL例外
	 */
	private Job toJobObject(final ResultSet rs) throws SQLException {
		Job ret = new Job();
		ret.setJobSeq(rs.getLong(COL_JOB_SEQ));
		ret.setJobId(rs.getString(COL_JOB_ID));
		ret.setJobSts(rs.getInt(COL_JOB_STS));
		ret.setUid(rs.getString(COL_USER_ID));
		ret.setIp(rs.getString(COL_IP_ADD));
		ret.setRemark(rs.getString(COL_BIKOU));
		ret.setJobParam(ParameterUtil.toArray(rs.getString(COL_JOB_PRM)));
		ret.setExecParam(ParameterUtil.toArray(rs.getString(COL_JOB_EXE_PRM)));
		ret.setDateTime(rs.getTimestamp(COL_TOROKU_DTM));
		ret.setGamenParam(rs.getString(COL_GAMEN_PRM));
		ret.setJobName(rs.getString(COL_JOB_MEI));
		ret.setHostId(rs.getString(COL_HOST_ID));
		ret.setMsgTxt(rs.getString(COL_MSG_TXT));
		return ret;
	}
}
